package com.cl.auth;

import com.cl.auth.stateless.StatelessUser;
import com.cl.dao.CommonDao;
import com.cl.dao.SearchDao;
import com.cl.entity.AccessToken;
import com.cl.entity.TokenRel;
import com.cl.entity.UGroup;
import com.cl.entity.UUser;
import com.cl.service.DTOHelper;
import com.cl.util.BaseException;
import com.cl.util.CommonUtil;
import com.cl.util.Constants;

import org.apache.commons.lang3.Validate;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Set;

// AccessToken表 id uid 
// TokenRel id uid key value time

/**
 * 用户权限的业务逻辑
 */
@Service
public class AuthServiceImpl implements AuthService {
	
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthServiceImpl.class);
    private static final int EXPIRES_IN = 8 * 60 * 60 * 1000;
    private static final int REPLAY_ATTACK_EXPIRE_SECOND = 5 * 60;
    private static final int ACCESS_TOKEN_TIME_TO_LIVE_SECOND = 30 * 60;
    private static final int REFRESH_TOKEN_TIME_TO_LIVE_SECOND = 24 * 60 * 60;

    @Autowired
    private SearchDao<?> searchDao;
    
    @Autowired
    private CommonDao commonDao;
    
    @Autowired
    private DTOHelper dtoHelper;

    @Autowired
    private CommonUtil commonUtil;

    /**
     * 校验请求合法，下面两种情况被认为是非法请求
     * <pre>
     * 如果时间戳距离现在已经超过5分钟
     * 如果随机数+'-'+时间戳的键已经存在
     * </pre>
     *
     * @param nonce     随机数
     * @param timestamp 时间戳
     * @return 合法请求返回true
     */
    @Override
    public boolean isNewRequest(String nonce, long timestamp) {
        long currentTime = System.currentTimeMillis();
        if (timestamp + REPLAY_ATTACK_EXPIRE_SECOND * 1000 < currentTime) {
            return false;
        }
//        String replayKey = "replay:" + nonce + ":" + timestamp;
        return true;
    }

    public AccessToken login(LoginVo command) {
        loginHandler(command);
        return tokenHandler(command.username);
    }

    @Override
    public void logout(String accessToken) {
        Validate.notNull(accessToken);
        Validate.notBlank(accessToken);
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        String key = getAccessKey(accessToken);
        TokenRel token = searchDao.getTokenRelByToken(key);
        commonDao.deleteDBObject(token);
        LOGGER.debug("remove token: {}", accessToken);
    }

    private String getAccessKey(String accessToken) {
        return "access:" + accessToken;
    }

    /**
     * 获取用户的授权
     *
     * @param userId 用户id
     * @return 授权的集合
     */
    @Override
    public Set<String> getPermissions(Long userId) {
        return null;
    }

    @Override
    public String getSecretKey(String accessToken) {
        String key = getAccessKey(accessToken);
        AccessToken token = searchDao.getAccessTokenByToken(key);
        return token.getSecretKey();
    }

    @Override
    public String getUsername(String accessToken) {
        String key = getAccessKey(accessToken);
        AccessToken token = searchDao.getAccessTokenByToken(key);
        return token.getUsername();
    }

    @Override
    public StatelessUser getUser(String accessToken) {
        String username = getUsername(accessToken);
        UUser user = searchDao.getUserByUid(username);
        StatelessUser statelessUser = new StatelessUser();
        statelessUser.setAccessToken(accessToken); 
        if(user!=null) {
            statelessUser.setId(user.getId());
			statelessUser.setUserId(user.getUserId());
            //statelessUser.setGid(user.getGid());
			statelessUser.setName(user.getName());
            statelessUser.setOrgId(user.getOrgId());
            statelessUser.setOrgName(commonUtil.getUserOrgNameByID(user.getOrgId()));
            statelessUser.setOfficeName(commonUtil.getUserOfficeNameByID(user.getOrgId()));
            statelessUser.setUserdn(user.getUserdn());
            statelessUser.setCorp(user.getCorp());
            statelessUser.setDuty(user.getDuty());
            statelessUser.setEmail(user.getEmail());
            //statelessUser.setGid(new BigDecimal(user.getUgroup().getId()));
            statelessUser.setRemark(user.getRemark());
            statelessUser.setSex(user.getSex());
            statelessUser.setIdentificationUnit(user.getIdentificationUnit());
            statelessUser.setUgroup(user.getUgroup());
	        //UGroup group = searchDao.getUserGroupByID(user.getGid().longValue());   
            UGroup group =user.getUgroup();
	        statelessUser.setRoles(dtoHelper.getListByGroup(group.getRoleid()));
	        statelessUser.setPermissions(dtoHelper.getSetByGroup(group.getPerms()));
			statelessUser.setGrantMenu(group.getGrantMenu());
        }
		return statelessUser;
    }

    private void loginHandler(LoginVo command) {
        Validate.notNull(command.username);
        Validate.notNull(command.password);
        Subject subject = SecurityUtils.getSubject();
        String password = new Md5Hash(command.password).toString();
        UsernamePasswordToken token = new UsernamePasswordToken(command.username, password);
        try {
            subject.login(token);
            LOGGER.debug("user login : {}", command.username);
        } catch (UnknownAccountException e) {
            LOGGER.info(command.username + "：未知用户");
            throw new AuthException("未知用户", e);
        } catch (IncorrectCredentialsException e) {
            LOGGER.info(command.username + "：密码不正确！");
        	throw new AuthException("密码不正确！", e);
        } catch (AuthenticationException e) {
            LOGGER.info(command.username + "：用户名或密码不正确！");
        	throw new AuthException("用户名或密码不正确！", e);
        } catch (Exception e) {
            LOGGER.info(command.username + "：用户名或密码不正确！");
        	throw new AuthException("用户或密码不正确！", e);
        }
    }

    private AccessToken tokenHandler(String username) {
        Validate.notNull(username);
        AccessToken accessToken = newToken(username);
        // 用户名，用户id, accessToken, accessKey, accessTokenTime
        // 用户名，用户id, refreshKey, accessToken, refreshTokenTime
        TokenRel accessKey = new TokenRel();
        accessKey.setTokenKey(getAccessKey(accessToken.getAccessToken()));
        accessKey.setTokenValue(accessToken.getId());
        accessKey.setServerTime(ACCESS_TOKEN_TIME_TO_LIVE_SECOND);// 简化后暂时未使用
        commonDao.saveDBOject(accessKey);
        TokenRel refreshKey = new TokenRel();
        refreshKey.setTokenKey(getRefreshKey(accessToken.getAccessToken()));
        refreshKey.setTokenValue(accessToken.getId());
        refreshKey.setServerTime(REFRESH_TOKEN_TIME_TO_LIVE_SECOND);// 简化后暂时未使用
        commonDao.saveDBOject(refreshKey);
        LOGGER.debug("crate new token : {}", accessToken.getAccessToken());
        return accessToken;
    }

    private String getRefreshKey(String accessToken) {
        return "refresh:" + accessToken;
    }

    /**
     * 创建AccessToken
     *
     * @param username 用户名
     * @return AccessToken
     */
    private AccessToken newToken(String username) {
        AccessToken token = new AccessToken();
        token.setUsername(username);
        UUser user = getUserByPwdLogin();
        token.setUId(user.getId());
        token.setUserid(user.getUserId());
        token.setAccessToken(createToken(username, EXPIRES_IN));
        token.setRefreshToken(createToken(username, EXPIRES_IN));
        token.setSecretKey(createToken(username, EXPIRES_IN));
        token.setExpiresIn("" + EXPIRES_IN);
        token.setServerTime("" + System.currentTimeMillis());
        commonDao.saveDBOject(token);        
        return token;
    }

    /**
     * 生成随机的TOKEN
     *
     * @param key       键
     * @param expiresIn 有效时间
     * @return token
     */
    private String createToken(String key, long expiresIn) {
        StringBuilder token = new StringBuilder(key);
        RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
        String salt = randomNumberGenerator.nextBytes().toHex();
        token.append(salt);
        token.append(System.currentTimeMillis() + expiresIn);
        return new SimpleHash(Constants.TOKEN_ALGORITHM_NAME, key, ByteSource.Util.bytes(token.toString()), Constants.TOKEN_HASH_ITERATIONS).toHex();
    }

    @Override
    public AccessToken refresh(RefreshVo command) {
        String accessToken = command.getAccessToken();
        String refreshToken = command.getRefreshToken();
        Validate.notBlank(accessToken);
        Validate.notBlank(refreshToken);

        String refreshCachekey = getRefreshKey(accessToken);
        AccessToken serverToken = searchDao.getAccessTokenByToken(refreshCachekey);
        if (serverToken != null && refreshToken.equals(serverToken.getRefreshToken())) {
        	TokenRel refreshTokenRel = searchDao.getTokenRelByToken(refreshCachekey);
        	commonDao.deleteDBObject(refreshTokenRel);
        	TokenRel accessTokenRel = searchDao.getTokenRelByToken(getAccessKey(accessToken));
        	commonDao.deleteDBObject(accessTokenRel);
            AccessToken token = tokenHandler(serverToken.getUsername());
            return token;
        }
        throw new BaseException("unlogin");
    }

	@Override
	public boolean getTokenAvailable(String command) {
		boolean available = false;
		AccessToken accessToken = null;
		String token = getAccessKey(command);
		accessToken = searchDao.getAccessTokenByToken(token);
		if (accessToken != null) {
			long serverTime = Long.parseLong(accessToken.getServerTime());
			long currentTime = System.currentTimeMillis();
			long expiresTime = Long.parseLong(accessToken.getExpiresIn());
			if (serverTime < currentTime && (currentTime - serverTime) < expiresTime) {
				available = true;
			}
		}
		return available;
	}

    private UUser getUserByPwdLogin(){
        Subject subject = SecurityUtils.getSubject();
        return (UUser) subject.getPrincipal();
    }

	@Override
	public void saveCasToken(UUser user, String casToken) {
		AccessToken token = new AccessToken();
		token.setUId(user.getId());
		token.setAccessToken(casToken);
		token.setRefreshToken(casToken);
		token.setSecretKey(casToken);
		token.setExpiresIn("" + EXPIRES_IN);
		token.setUsername(user.getName());
		token.setUserid(user.getUserId());
		token.setServerTime("" + System.currentTimeMillis());
		commonDao.saveDBOject(token);
		Long tokenId = token.getId();
		TokenRel accessRel = new TokenRel();
		accessRel.setTokenKey(getAccessKey(casToken));
		accessRel.setTokenValue(tokenId);
		accessRel.setServerTime(ACCESS_TOKEN_TIME_TO_LIVE_SECOND);
		commonDao.saveDBOject(accessRel);
		TokenRel refreshRel = new TokenRel();
		refreshRel.setTokenKey(getRefreshKey(casToken));
		refreshRel.setTokenValue(tokenId);
		refreshRel.setServerTime(REFRESH_TOKEN_TIME_TO_LIVE_SECOND);
		LOGGER.debug("save cas token : {}", casToken);
		commonDao.saveDBOject(refreshRel);
	}
	
}